﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JXT.PrimaryKey.Batman.Extensions;

namespace JXT.PrimaryKey.Batman.Core.Security
{
    public static class SecurityExtensions
    {
        /// <summary>
        /// 检查某个具体权限
        /// </summary>
        /// <param name="membership">权限上下文对象</param>
        /// <param name="name">权限名称（不区分大小写）</param>
        /// <param name="expectedValue">期望值</param>
        /// <param name="type">与期望值的判断方式</param>
        /// <returns>检查结果</returns>
        public static bool Check(this IMembership membership, string name, object expectedValue, AuthorizationType type = AuthorizationType.Equal)
        {
            return membership.Authorities.Check(name, expectedValue, type);
        }

        /// <summary>
        /// 检查某个具体权限
        /// </summary>
        /// <param name="source">权限集合</param>
        /// <param name="name">权限名称（不区分大小写）</param>
        /// <param name="expectedValue">期望值</param>
        /// <param name="type">与期望值的判断方式</param>
        /// <returns>检查结果</returns>
        public static bool Check(this IEnumerable<Authority> source, string name, object expectedValue, AuthorizationType type = AuthorizationType.Equal)
        {
            var find = source.FirstOrDefault(e => e.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
            if (find == null)
                return false;
            if (find.ExpiresTime != null && DateTime.Now > find.ExpiresTime)
                return false;
            switch (find.Type)
            {
                case AuthorityType.Boolean:
                default:
                    return checkBoolean(find.Value, expectedValue, type);
                case AuthorityType.Double:
                    return checkDouble(find.Value, expectedValue, type);
                case AuthorityType.Byte:
                    return checkByte(find.Value, expectedValue, type);
                case AuthorityType.Int32:
                    return checkInt32(find.Value, expectedValue, type);
            }
        }

        /// <summary>
        /// 将多个可重复权限列表转换为单项列表
        /// </summary>
        /// <remarks>
        /// 如role1和role2都包含islock权限，而用户a同时拥有role1和role2，那么直接获取权限时，会出项两个重复的islock权限，而本方法将根据实际设置合并两个（或者更多）权限设置
        /// </remarks>
        /// <param name="source">原始列表</param>
        /// <returns>转换后的列表</returns>
        public static Authority[] ToRealAuthorities(this IEnumerable<Authority> source)
        {
            if (source == null || source.Count() <= 0)
                return new Authority[] { };
            Dictionary<int, List<Authority>> dic = new Dictionary<int, List<Authority>>();
            foreach (var item in source)
            {
                if (!dic.ContainsKey(item.TemplateId))
                {
                    List<Authority> list = new List<Authority>();
                    list.Add(item);
                    dic.Add(item.TemplateId, list);
                }
                else
                {
                    dic[item.TemplateId].Add(item);
                }
            }
            List<Authority> resultList = new List<Authority>();
            foreach (var item in dic)
            {
                if (item.Value.Count <= 1)
                {
                    resultList.Add(item.Value[0]);
                }
                else
                {
                    var first = item.Value.First();
                    switch (first.Type)
                    {
                        case AuthorityType.Boolean:
                            resultList.Add(fixBoolean(item.Value));
                            break;
                        case AuthorityType.Byte:
                            resultList.Add(fixByte(item.Value));
                            break;
                        case AuthorityType.Double:
                            resultList.Add(fixDouble(item.Value));
                            break;
                        case AuthorityType.Int32:
                            resultList.Add(fixInt32(item.Value));
                            break;
                    }
                }
            }
            return resultList.ToArray();
        }

        #region private for Check method

        private static bool checkBoolean(string stringValue, object expectedValue, AuthorizationType type)
        {
            bool value = stringValue.ToBoolean();
            if (!(expectedValue is Boolean))
            {
                return false;
            }
            bool expetctedBoolean = (bool)expectedValue;
            switch (type)
            {
                case AuthorizationType.Equal:
                    return value == expetctedBoolean;
                case AuthorizationType.NotEqual:
                    return value != expetctedBoolean;
                default:
                    return false;
            }
        }

        private static bool checkInt32(string stringValue, object expectedValue, AuthorizationType type)
        {
            int value = stringValue.ToInt32();
            if (!(expectedValue is Int32))
            {
                return false;
            }
            int expectedInt32 = (int)expectedValue;
            switch (type)
            {
                case AuthorizationType.Equal:
                    return value == expectedInt32;
                case AuthorizationType.Less:
                    return value < expectedInt32;
                case AuthorizationType.LessOrEqual:
                    return value <= expectedInt32;
                case AuthorizationType.More:
                    return value > expectedInt32;
                case AuthorizationType.MoreOrEqual:
                    return value >= expectedInt32;
                case AuthorizationType.NotEqual:
                    return value != expectedInt32;
                default:
                    return false;
            }
        }

        private static bool checkDouble(string stringValue, object expectedValue, AuthorizationType type)
        {
            double value = stringValue.ToDouble();
            if (!(expectedValue is Double))
            {
                return false;
            }
            double expectedDouble = (double)expectedValue;
            switch (type)
            {
                case AuthorizationType.Equal:
                    return value == expectedDouble;
                case AuthorizationType.Less:
                    return value < expectedDouble;
                case AuthorizationType.LessOrEqual:
                    return value <= expectedDouble;
                case AuthorizationType.More:
                    return value > expectedDouble;
                case AuthorizationType.MoreOrEqual:
                    return value >= expectedDouble;
                case AuthorizationType.NotEqual:
                    return value != expectedDouble;
                default:
                    return false;
            }
        }

        private static bool checkByte(string stringValue, object expectedValue, AuthorizationType type)
        {
            byte value = stringValue.ToByte();
            if (!(expectedValue is byte))
            {
                return false;
            }
            byte expectedByte = (byte)expectedValue;
            switch (type)
            {
                case AuthorizationType.Equal:
                    return value == expectedByte;
                case AuthorizationType.Less:
                    return value < expectedByte;
                case AuthorizationType.LessOrEqual:
                    return value <= expectedByte;
                case AuthorizationType.More:
                    return value > expectedByte;
                case AuthorizationType.MoreOrEqual:
                    return value >= expectedByte;
                case AuthorizationType.NotEqual:
                    return value != expectedByte;
                default:
                    return false;
            }
        }

        #endregion

        #region private for ToRealAuthorities method

        private static Authority fixBoolean(IEnumerable<Authority> source)
        {
            var first = source.FirstOrDefault();
            Authority result = createNewAuthority(first);
            if (first.Priority == PriorityType.AllowedFirst)
            {
                bool boolValue = true;
                foreach (var item in source)
                {
                    boolValue = boolValue || Convert.ToBoolean(item.Value);
                }
                result.Value = boolValue.ToString().ToLower();
            }
            else
            {
                bool boolValue = true;
                foreach (var item in source)
                {
                    boolValue = boolValue && Convert.ToBoolean(item.Value);
                }
                result.Value = boolValue.ToString().ToLower();
            }
            return result;
        }

        private static Authority fixByte(IEnumerable<Authority> source)
        {
            var first = source.FirstOrDefault();
            Authority result = createNewAuthority(first);
            if (first.Priority == PriorityType.AllowedFirst)
            {
                byte byteValue = 0;
                foreach (var item in source)
                {
                    byte currentValue = Convert.ToByte(item.Value);
                    byteValue = currentValue > byteValue ? currentValue : byteValue;
                }
                result.Value = byteValue.ToString();
            }
            else
            {
                byte byteValue = 0;
                foreach (var item in source)
                {
                    byte currentValue = Convert.ToByte(item.Value);
                    byteValue = currentValue < byteValue ? currentValue : byteValue;
                }
                result.Value = byteValue.ToString();
            }
            return result;
        }

        private static Authority fixInt32(IEnumerable<Authority> source)
        {
            var first = source.FirstOrDefault();
            Authority result = createNewAuthority(first);
            if (first.Priority == PriorityType.AllowedFirst)
            {
                int int32Value = 0;
                foreach (var item in source)
                {
                    int currentValue = Convert.ToInt32(item.Value);
                    int32Value = currentValue > int32Value ? currentValue : int32Value;
                }
                result.Value = int32Value.ToString();
            }
            else
            {
                int int32Value = 0;
                foreach (var item in source)
                {
                    int currentValue = Convert.ToInt32(item.Value);
                    int32Value = currentValue < int32Value ? currentValue : int32Value;
                }
                result.Value = int32Value.ToString();
            }
            return result;
        }

        private static Authority fixDouble(IEnumerable<Authority> source)
        {
            var first = source.FirstOrDefault();
            Authority result = createNewAuthority(first);
            if (first.Priority == PriorityType.AllowedFirst)
            {
                double doubleValue = 0;
                foreach (var item in source)
                {
                    double currentValue = Convert.ToDouble(item.Value);
                    doubleValue = currentValue > doubleValue ? currentValue : doubleValue;
                }
                result.Value = doubleValue.ToString();
            }
            else
            {
                double doubleValue = 0;
                foreach (var item in source)
                {
                    double currentValue = Convert.ToDouble(item.Value);
                    doubleValue = currentValue < doubleValue ? currentValue : doubleValue;
                }
                result.Value = doubleValue.ToString();
            }
            return result;
        }

        private static Authority createNewAuthority(Authority first)
        {
            Authority result = new Authority();
            result.TemplateId = first.TemplateId;
            result.Name = first.Name;
            result.Summary = first.Summary;
            result.Type = first.Type;
            result.Priority = first.Priority;
            return result;
        }

        #endregion
    }
}
